package org.farng.mp3.id3;

import org.farng.mp3.InvalidTagException;
import org.farng.mp3.TagConstant;
import org.farng.mp3.TagUtility;
import org.farng.mp3.lyrics3.FieldBodyAUT;
import org.farng.mp3.lyrics3.FieldBodyEAL;
import org.farng.mp3.lyrics3.FieldBodyEAR;
import org.farng.mp3.lyrics3.FieldBodyETT;
import org.farng.mp3.lyrics3.FieldBodyINF;
import org.farng.mp3.lyrics3.FieldBodyLYR;
import org.farng.mp3.lyrics3.Lyrics3v2Field;
import org.farng.mp3.object.ObjectLyrics3Line;

import java.io.IOException;
import java.io.RandomAccessFile;

import java.util.Iterator;


/**
 * Title:       ID3v2_4Frame Description: This class is the tag frame header
 * used for ID3v2.40 tags Copyright:   Copyright (c) 2002 Company:
 *
 * @author Eric Farng
 * @version $Revision: 1.17 $
 */
public class ID3v2_4Frame extends ID3v2_3Frame {
    /** DOCUMENT ME! */
    protected boolean dataLengthIndicator = false;

    /** DOCUMENT ME! */
    protected boolean unsynchronization = false;

    /**
     * Creates a new ID3v2_4Frame object.
     */
    public ID3v2_4Frame() {}

    public ID3v2_4Frame(ID3v2_4Frame copyObject) {
        super(copyObject);
        this.dataLengthIndicator = copyObject.dataLengthIndicator;
        this.unsynchronization   = copyObject.unsynchronization;
    }

    /**
     * Creates a new ID3v2_4Frame object.
     *
     * @param body DOCUMENT ME!
     */
    public ID3v2_4Frame(AbstractID3v2FrameBody body) {
        this.body = body;
    }

    /**
     * Creates a new ID3v2_4Frame object.
     *
     * @param frame DOCUMENT ME!
     */
    public ID3v2_4Frame(AbstractID3v2Frame frame) {
        if (frame instanceof ID3v2_4Frame) {
            ID3v2_4Frame f = (ID3v2_4Frame) frame;
            this.unsynchronization   = f.unsynchronization;
            this.dataLengthIndicator = f.dataLengthIndicator;
        }

        if (frame instanceof ID3v2_3Frame) {
            // a id3v2_4 frame is of type id3v2_3 frame also ...
            ID3v2_3Frame f = (ID3v2_3Frame) frame;
            this.tagAlterPreservation  = f.tagAlterPreservation;
            this.fileAlterPreservation = f.fileAlterPreservation;
            this.readOnly              = f.readOnly;
            this.groupingIdentity      = f.groupingIdentity;
            this.compression           = f.compression;
            this.encryption            = f.encryption;
        }

        if (frame instanceof ID3v2_2Frame) {
            // no variables yet
        }

        if (frame.getBody() == null) {
            // do nothing
        } else if (TagUtility.isID3v2_4FrameIdentifier(frame.getIdentifier())) {
            this.body = (AbstractID3v2FrameBody) TagUtility.copyObject(frame.getBody());
        }
    }

    /**
     * Creates a new ID3v2_4Frame object.
     *
     * @param readOnly DOCUMENT ME!
     * @param groupingIdentity DOCUMENT ME!
     * @param compression DOCUMENT ME!
     * @param encryption DOCUMENT ME!
     * @param unsynchronization DOCUMENT ME!
     * @param dataLengthIndicator DOCUMENT ME!
     * @param body DOCUMENT ME!
     */
    public ID3v2_4Frame(boolean readOnly, boolean groupingIdentity, boolean compression, boolean encryption, boolean unsynchronization, boolean dataLengthIndicator, AbstractID3v2FrameBody body) {
        this.body                = body;
        this.readOnly            = readOnly;
        this.groupingIdentity    = groupingIdentity;
        this.compression         = compression;
        this.encryption          = encryption;
        this.unsynchronization   = unsynchronization;
        this.dataLengthIndicator = dataLengthIndicator;
    }

    /**
     * Creates a new ID3v2_4Frame object.
     *
     * @param field DOCUMENT ME!
     *
     * @throws InvalidTagException DOCUMENT ME!
     */
    public ID3v2_4Frame(Lyrics3v2Field field)
                 throws InvalidTagException {
        String id = field.getIdentifier();
        String value;

        if (id.equals("IND")) {
            throw new InvalidTagException("Cannot create ID3v2.40 frame from Lyrics3 indications field.");
        } else if (id.equals("LYR")) {
            FieldBodyLYR      lyric = (FieldBodyLYR) field.getBody();

            ObjectLyrics3Line line;
            Iterator          iterator = lyric.iterator();
            FrameBodySYLT     sync;
            FrameBodyUSLT     unsync;
            boolean           hasTimeStamp = lyric.hasTimeStamp();

            // we'll create only one frame here.
            // if there is any timestamp at all, we will create a sync'ed frame.
            sync   = new FrameBodySYLT((byte) 0, "ENG", (byte) 2, (byte) 1, "");
            unsync = new FrameBodyUSLT((byte) 0, "ENG", "", "");

            while (iterator.hasNext()) {
                line = (ObjectLyrics3Line) iterator.next();

                if (hasTimeStamp) {
                    sync.addLyric(line);
                } else {
                    unsync.addLyric(line);
                }
            }

            if (hasTimeStamp) {
                this.body = sync;
            } else {
                this.body = unsync;
            }
        } else if (id.equals("INF")) {
            value     = ((FieldBodyINF) field.getBody()).getAdditionalInformation();
            this.body = new FrameBodyCOMM((byte) 0, "ENG", "", value);
        } else if (id.equals("AUT")) {
            value     = ((FieldBodyAUT) field.getBody()).getAuthor();
            this.body = new FrameBodyTCOM((byte) 0, value);
        } else if (id.equals("EAL")) {
            value     = ((FieldBodyEAL) field.getBody()).getAlbum();
            this.body = new FrameBodyTALB((byte) 0, value);
        } else if (id.equals("EAR")) {
            value     = ((FieldBodyEAR) field.getBody()).getArtist();
            this.body = new FrameBodyTPE1((byte) 0, value);
        } else if (id.equals("ETT")) {
            value     = ((FieldBodyETT) field.getBody()).getTitle();
            this.body = new FrameBodyTIT2((byte) 0, value);
        } else if (id.equals("IMG")) {
            throw new InvalidTagException("Cannot create ID3v2.40 frame from Lyrics3 image field.");
        } else {
            throw new InvalidTagException("Cannot caret ID3v2.40 frame from " + id + " Lyrics3 field");
        }
    }

    /**
     * Creates a new ID3v2_4Frame object.
     *
     * @param file DOCUMENT ME!
     *
     * @throws IOException DOCUMENT ME!
     * @throws InvalidTagException DOCUMENT ME!
     */
    public ID3v2_4Frame(RandomAccessFile file)
                 throws IOException, InvalidTagException {
        this.read(file);
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public int getSize() {
        return body.getSize() + 4 + 2 + 4;
    }

    /**
     * DOCUMENT ME!
     *
     * @param obj DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public boolean equals(Object obj) {
        if ((obj instanceof ID3v2_4Frame) == false) {
            return false;
        }

        ID3v2_4Frame object = (ID3v2_4Frame) obj;

        if (this.unsynchronization != object.unsynchronization) {
            return false;
        }

        if (this.dataLengthIndicator != object.dataLengthIndicator) {
            return false;
        }

        return super.equals(obj);
    }

    /**
     * DOCUMENT ME!
     *
     * @param file DOCUMENT ME!
     *
     * @throws IOException DOCUMENT ME!
     * @throws InvalidTagException DOCUMENT ME!
     */
    public void read(RandomAccessFile file)
              throws IOException, InvalidTagException {
        long   filePointer;
        byte[] buffer = new byte[4];
        byte   b;

        // lets scan for a non-zero byte;
        do {
            filePointer = file.getFilePointer();
            b           = file.readByte();
            org.farng.mp3.id3.AbstractID3v2.paddingCounter++;
        } while (b == 0);

        file.seek(filePointer);
        org.farng.mp3.id3.AbstractID3v2.paddingCounter--;

        // read the four character identifier
        file.read(buffer, 0, 4);

        String identifier = new String(buffer, 0, 4);

        // is this a valid identifier?
        if (isValidID3v2FrameIdentifier(identifier) == false) {
            file.seek(file.getFilePointer() - 3);
            throw new InvalidTagException(identifier + " is not a valid ID3v2.40 frame");
        }

        filePointer = file.getFilePointer();

        // skip the 4 byte size
        file.skipBytes(4);

        // read the flag bytes
        file.read(buffer, 0, 2);

        tagAlterPreservation  = (buffer[0] & TagConstant.MASK_V24_TAG_ALTER_PRESERVATION) != 0;
        fileAlterPreservation = (buffer[0] & TagConstant.MASK_V24_FILE_ALTER_PRESERVATION) != 0;
        readOnly              = (buffer[0] & TagConstant.MASK_V24_READ_ONLY) != 0;

        groupingIdentity    = (buffer[1] & TagConstant.MASK_V24_GROUPING_IDENTITY) != 0;
        compression         = (buffer[1] & TagConstant.MASK_V24_COMPRESSION) != 0;
        encryption          = (buffer[1] & TagConstant.MASK_V24_ENCRYPTION) != 0;
        unsynchronization   = (buffer[1] & TagConstant.MASK_V24_FRAME_UNSYNCHRONIZATION) != 0;
        dataLengthIndicator = (buffer[1] & TagConstant.MASK_V24_DATA_LENGTH_INDICATOR) != 0;

        file.seek(filePointer);
        body = readBody(identifier, file);
    }

    /**
     * DOCUMENT ME!
     *
     * @param file DOCUMENT ME!
     *
     * @throws IOException DOCUMENT ME!
     */
    public void write(RandomAccessFile file)
               throws IOException {
        byte[] buffer = new byte[4];
        long   filePointer;

        String str = TagUtility.truncate(getIdentifier(), 4);

        for (int i = 0; i < str.length(); i++) {
            buffer[i] = (byte) str.charAt(i);
        }

        file.write(buffer, 0, str.length());

        filePointer = file.getFilePointer();

        // skip the size bytes
        file.skipBytes(4);

        setAlterPreservation();

        buffer[0] = 0;
        buffer[1] = 0;

        if (tagAlterPreservation) {
            buffer[0] |= TagConstant.MASK_V24_TAG_ALTER_PRESERVATION;
        }

        if (fileAlterPreservation) {
            buffer[0] |= TagConstant.MASK_V24_FILE_ALTER_PRESERVATION;
        }

        if (readOnly) {
            buffer[0] |= TagConstant.MASK_V24_READ_ONLY;
        }

        if (groupingIdentity) {
            buffer[1] |= TagConstant.MASK_V24_GROUPING_IDENTITY;
        }

        if (compression) {
            buffer[1] |= TagConstant.MASK_V24_COMPRESSION;
        }

        if (encryption) {
            buffer[1] |= TagConstant.MASK_V24_ENCRYPTION;
        }

        if (unsynchronization) {
            buffer[1] |= TagConstant.MASK_V24_FRAME_UNSYNCHRONIZATION;
        }

        if (dataLengthIndicator) {
            buffer[1] |= TagConstant.MASK_V24_DATA_LENGTH_INDICATOR;
        }

        file.write(buffer, 0, 2);

        file.seek(filePointer);
        body.write(file);
    }
}